/*
 * Copyright Iain Sproat, 2008
 * 
 *
 */
using System;
using System.Collections.Generic;

namespace FreeBase.Fluent
{
	/// <summary>
	/// This allows criteria, such as comparisons, to be used in a fluent way
	/// </summary>
	public sealed class FBCriteria : FBInstruction
	{
		private FBQuery parentQuery;
		private ComparisonOperator comparisonOp = ComparisonOperator.Is;
		private string[] comparisonValues = {string.Empty}; //an array allows us to easily create an OR query when necessary
		private JoinType joinType = JoinType.AND;
		private bool _isNumericComparison = false;
		
		internal FBCriteria( FBQuery fbQuery )
		{
			if( fbQuery == null )
				throw new ArgumentNullException( "fbQuery" );
			
			this.parentQuery = fbQuery;
		}
		
		#region Fluent methods for exposing common properties
		
		public FBCriteria Name
		{
			get{
				this.PropertyName = "name";
				return this;
			}
		}
		
		public FBCriteria Key
		{
			get{
				this.PropertyName = "key";
				return this;
			}
		}
		
		public FBCriteria Id
		{
			get{
				this.PropertyName = "id";
				return this;
			}
		}
		public FBCriteria Guid
		{
			get{
				this.PropertyName = "guid";
				return this;
			}
		}
		
		public FBCriteria Type
		{
			get{
				this.PropertyName = "type";
				return this;
			}
		}
		
		public FBCriteria Timestamp
		{
			get{
				this.PropertyName = "timestamp";
				return this;
			}
		}
		
		public FBCriteria Creator
		{
			get{
				this.PropertyName = "creator";
				return this;
			}
		}
		
		public FBCriteria Permission
		{
			get{
				this.PropertyName = "permission";
				return this;
			}
		}
		
		public FBCriteria Value
		{
			get{
				this.PropertyName = "value";
				return this;
			}
		}
		
		public FBCriteria Language
		{
			get{
				this.PropertyName = "lang";
				return this;
			}
		}
		
		public FBCriteria MasterProperty
		{
			get{
				this.PropertyName = "master_property";
				return this;
			}
		}
		
		public FBCriteria ReverseProperty
		{
			get{
				this.PropertyName = "reverse_property";
				return this;
			}
		}
		
		public FBCriteria ExpectedType
		{
			get{
				this.PropertyName = "expected_type";
				return this;
			}
		}
		
		public FBCriteria Unit
		{
			get{
				this.PropertyName = "unit";
				return this;
			}
		}
		
		public FBCriteria Schema
		{
			get{
				this.PropertyName = "schema";
				return this;
			}
		}
		
		public FBCriteria Unique
		{
			get{
				this.PropertyName = "unique";
				return this;
			}
		}

		public FBCriteria Delegated
		{
			get{
				this.PropertyName = "delegated";
				return this;
			}
		}
		
		public FBCriteria Enumeration
		{
			get{
				this.PropertyName = "enumeration";
				return this;
			}
		}
		
		public FBCriteria Links
		{
			get{
				this.PropertyName = "links";
				return this;
			}
		}
		#endregion

		#region Fluent Members
		public FBCriteria Property( string propertyName )
		{
			if( string.IsNullOrEmpty( propertyName ) )
				throw new ArgumentNullException( "propertyName" );
			this.PropertyName = propertyName.ToLower();
			return this;
		}
		
		/// <summary>
		/// This is an equality comparison
		/// </summary>
		/// <param name="comparison"></param>
		/// <returns></returns>
		public FBQuery Is( params string[] comparison )
		{
			if( comparison == null )
				throw new ArgumentNullException( "comparison" );
			if( comparison.Length == 0 )
				throw new ArgumentException( "please provide one or more arguments" );
			if( string.IsNullOrEmpty( this.PropertyName ) )
				throw new ApplicationException( "Please use the Where() method before calling Is()" );
			
			this.comparisonOp = ComparisonOperator.Is; //equal is the default.
			if( comparison.Length > 1 )
				this.JoinType = JoinType.OR;
			this.comparisonValues = comparison;
			this.parentQuery.AddCriteria( this );
			return parentQuery;
		}
		
		public FBQuery Is( params int[] comparison )
		{
			if( comparison == null )
				throw new ArgumentNullException( "comparison" );
			if( comparisonValues.Length == 0 )
				throw new ArgumentException( "Please provide one or more arguments" );
			
			this._isNumericComparison = true;
			string[] converted = new string[comparison.Length];
			for(int i = 0; i < comparison.Length; i++ )
			{
					converted[i] = comparison[i].ToString();
			}
			return this.Is( converted );
		}
		
		public FBQuery Is( params double[] comparison )
		{
			if( comparison == null )
				throw new ArgumentNullException( "comparison" );
			if( comparisonValues.Length == 0 )
				throw new ArgumentException( "Please provide one or more arguments" );
			
			this._isNumericComparison = true;
			string[] converted = new string[comparison.Length];
			for(int i = 0; i < comparison.Length; i++ )
				converted[i] = comparison[i].ToString();
			return this.Is( converted );
		}
		
		/// <summary>
		/// This is a negative equality comparison
		/// </summary>
		/// <param name="comparison"></param>
		/// <returns></returns>
		public FBQuery IsNot( params string[] comparison )
		{
			FBQuery ret = this.Is(comparison );
			this.comparisonOp = ComparisonOperator.IsNot;
			this.parentQuery.AddCriteria( this );
			return ret;
		}
		
		public FBQuery IsGreaterThan( int comparison )
		{
			this._isNumericComparison = true;
			return this.IsGreaterThan( comparison.ToString() );
		}
		
		public FBQuery IsGreaterThan( double comparison )
		{
			this._isNumericComparison = true;
			return this.IsGreaterThan( comparison.ToString() );
		}
		
		public FBQuery IsGreaterThan( string comparison )
		{
			if( string.IsNullOrEmpty(comparison) )
				throw new ArgumentNullException( "comparison" );
			if( string.IsNullOrEmpty( this.PropertyName ) )
				throw new ApplicationException( "Please use the Where() method before calling IsGreaterThan()" );
			
			this.comparisonOp = ComparisonOperator.IsGreaterThan;
			this.comparisonValues = new string[]{comparison};
			this.parentQuery.AddCriteria( this );
			return parentQuery;
		}
		
		public FBQuery IsLessThan( int comparison )
		{
			this._isNumericComparison = true;
			return this.IsLessThan( comparison.ToString() );
		}
		public FBQuery IsLessThan( decimal comparison )
		{
			this._isNumericComparison = true;
			return this.IsLessThan( comparison.ToString( ) );
		}
		public FBQuery IsLessThan( double comparison )
		{
			this._isNumericComparison = true;
			return this.IsLessThan( comparison.ToString() );
		}
		public FBQuery IsLessThan( string comparison )
		{
			if( string.IsNullOrEmpty(comparison) )
				throw new ArgumentNullException( "comparison" );
			if( string.IsNullOrEmpty( this.PropertyName ) )
				throw new ApplicationException( "Please use the Where() method before calling IsGreaterThan()" );
			
			this.comparisonOp = ComparisonOperator.IsLessThan;
			this.comparisonValues = new string[]{comparison};
			this.parentQuery.AddCriteria( this );
			return parentQuery;
		}
		
		public FBQuery IsLessThanOrEqual( int comparison )
		{
			this._isNumericComparison = true;
			return this.IsLessThanOrEqual( comparison.ToString() );
		}
		public FBQuery IsLessThanOrEqual( decimal comparison )
		{
			this._isNumericComparison = true;
			return this.IsLessThanOrEqual( comparison.ToString( ) );
		}
		public FBQuery IsLessThanOrEqual( double comparison )
		{
			this._isNumericComparison = true;
			return this.IsLessThanOrEqual( comparison.ToString() );
		}
		public FBQuery IsLessThanOrEqual( string comparison )
		{
			if( string.IsNullOrEmpty(comparison) )
				throw new ArgumentNullException( "comparison" );
			if( string.IsNullOrEmpty( this.PropertyName ) )
				throw new ApplicationException( "Please use the Where() method before calling IsGreaterThan()" );
			
			this.comparisonOp = ComparisonOperator.IsLessOrEqualTo;
			this.comparisonValues = new string[]{comparison};
			this.parentQuery.AddCriteria( this );
			return parentQuery;
		}
		/// <summary>
		/// StartsWith is a textual comparer
		/// </summary>
		/// <param name="comparison"></param>
		/// <returns></returns>
		public FBQuery StartsWith( string comparison )
		{
			if( string.IsNullOrEmpty( comparison ) )
				throw new ArgumentNullException( comparison );
			if( string.IsNullOrEmpty( this.PropertyName ) )
				throw new ApplicationException( "Please use the Where() method before calling StartsWith()" );
			
			this.comparisonOp = ComparisonOperator.TextualComparison;
			this.comparisonValues[0] = "^" + comparison;
			this.parentQuery.AddCriteria( this );
			return this.parentQuery;
		}
		
		/// <summary>
		/// Contains is a textual comparison, which looks through strings to find if it contains a particular sequence
		/// </summary>
		/// <param name="comparison"></param>
		/// <returns></returns>
		public FBQuery Contains( string comparison )
		{
			if( string.IsNullOrEmpty( comparison ) )
				throw new ArgumentNullException( "comparison" );
			if( string.IsNullOrEmpty( this.PropertyName ) )
				throw new ApplicationException( "Please use the Where() method before calling Contains()" );
			
			this.comparisonOp = ComparisonOperator.TextualComparison;
			this.comparisonValues[0] = comparison;
			this.parentQuery.AddCriteria( this );
			return this.parentQuery;
		}
		
		public FBQuery IsAProperty
		{
			get{
				return this.Is( "/type/property" );
			}
		}
		
		
		
		#endregion
		
		#region Internal Properties used for working with criteria
		//internal string PropertyName{
		//	get{ return this.property; }
		//	set{ this.property = value; }
		//}
		internal ComparisonOperator GetOperator{
			get{ return this.comparisonOp; }
		}
		internal string GetValue{
			get{ return this.comparisonValues[0]; }
		}
		
		internal string[] GetValues{
			get{ return this.comparisonValues; }
		}
		
		internal JoinType JoinType{
			get{ return this.joinType; }
			set{ this.joinType = value; }
		}
		
		internal bool IsNumericComparison
		{
			get{ return this._isNumericComparison; }
		}
		#endregion
	}
	
	internal enum JoinType
	{
		AND,
		OR
	}
	
	internal enum ComparisonOperator
	{
		Is,
		IsNot,
		IsGreaterThan,
		IsLessThan,
		IsGreaterOrEqualTo,
		IsLessOrEqualTo,
		TextualComparison,
	}
}
